<?php
/**
 * This class provides a simple interface to the PhoneWars webservice
 * functionality. 
 *
 * Usage:
 * PhoneWars uses PHP session id's to keep track of the users.
 * Below is an example usage of the PhoneWars module:
 * <code>
 * session_start();
 * $pw = new PhoneWars();
 * $pw->begin(session_id());
 * switch($_POST['PW_COMMAND'])
 * {
 *   case 'SET_NAME':
 *     print($pw->setName($_POST['name']));
 *     break;
 * }
 * </code>
 *
 * The library requires PHP >= 5.1.2 with curl or http/https stream wrappers enabled.
 * @author Kevin M. Smith and Chris Bobo
 * @copyright Copyright (c) 2012, Kevin M. Smith and Chris Bobo
 */

define("UPDATE_POSITION",       0);
define("UPDATE_GUN_SHOT",       1);
define("UPDATE_MINE_LAY",       2);
define("UPDATE_MINE_DETONATE",  3);
define("UPDATE_KILLED_BY_MINE", 4);
define("UPDATE_KILLED_BY_GUN",  5);
define("UPDATE_PLAYER_JOINED",  6);
define("UPDATE_LEAVE_MATCH",    7);
define("UPDATE_GAME_OVER",      8);
define("UPDATE_DELETE_MATCH",   9);

class PhoneWars
{
	private $user = "kevin4";
	private $password = "100688";
	private $database = "kevin4";
	private $host = "localhost";
	private $error_log_file = "./pw_error.log";
	private $max_idle_time = 3600;

	private $sessionid;
	private $account_id;
	private $account_rank;

	public function __construct()
	{
		
	}

	/**
	 * Begin the PhoneWars service session. It can check whether it is a known user.
	 * checkSession will usually only be set to FALSE if the user is in the process of being logged in
	 * @param $sessionid PHP Session ID
	 * @param $checkSession Whether to check if it is a known session
	 * @throws ErrorException
	 */ 
	public function begin($sessionid,$checkSession=TRUE)
	{
		if(!mysql_connect ($this->host,$this->user,$this->password,FALSE,2))
		{
			$this->error("Not connected : " . mysql_error());
		}

		if(!mysql_select_db($this->database))
		{
			$this->error("Can't use database, '$this->database' : " . mysql_error());
		}

		$this->sessionid = $sessionid;
		if($checkSession)
		{
			$info = $this->findAccount();
			$this->account_id = $info['account_id'];
			$this->account_rank = $info['fk_account_rank_id'];
		}
	}

	/**
	 * End the PhoneWars service session. It closes the MySQL connection.
	 */ 
	public function end()
	{
		mysql_close();
	}

	/**
	 * Used for error logging and throws an ErrorException.
	 * @param $message The message to be logged and thrown in the exception.
	 * @throws ErrorException
	 */ 
	private function error($message)
	{
		error_log("PW_ERROR: ".$message."\n", 3, $this->error_log_file);
		throw new ErrorException($message);
	}

	/**
	 * Execute a MySQL query.
	 * @param $query The query to execute.
	 * @throws ErrorException
	 */ 
	private function exec_query($query)
	{
		// Perform Query
		$result = mysql_query($query);

		// This shows the actual query sent to MySQL, and the error. Useful for debugging.
		if (!$result)
		{
			$message  = 'Invalid query: ' . mysql_error() . "\n";
			$message .= 'Whole query: ' . $query;
			$this->error($message);
		}

		return $result;
	}


	/**
	 * Login with an openid and sessionid. If the account does not exist yet it
	 * will be created.
	 * @param $openid The openid of the user.
	 * @throws ErrorException
	 */ 
	// 
	public function loginAccount($openid)
	{
		$check_account = sprintf("SELECT account_id FROM Accounts WHERE openid='%s'",
			mysql_real_escape_string($openid));

		$add_account = sprintf("INSERT INTO Accounts (openid,name,fk_account_rank_id) VALUES ('%s','Player',1) ON DUPLICATE KEY UPDATE account_id=account_id",
			mysql_real_escape_string($openid));

		$login_account = sprintf("UPDATE Accounts SET last_active=CURRENT_TIMESTAMP, sessionid='%s' WHERE openid='%s'",
			mysql_real_escape_string($this->sessionid),
			mysql_real_escape_string($openid));

		// If the account does not exist, create it.
		$result_set = $this->exec_query($check_account);
		if(mysql_num_rows($result_set) == 0)
		{
			$this->exec_query($add_account);
		}

		// Login the account.
		$this->exec_query($login_account);
	}

	/**
	 * Log out the user's account by setting the session id to null.
	 * @throws ErrorException
	 */ 
	public function logoutAccount()
	{
		$logout_account = sprintf("UPDATE Accounts SET sessionid=NULL WHERE account_id=%d",
			mysql_real_escape_string($this->account_id));

		$this->exec_query($logout_account);
	}

	/**
	 * Find a specific user's account in the Accounts Table based on the user's
	 * session id and their last seen timestamp.
	 * @throws ErrorException
	 */ 
	private function findAccount()
	{
		$select_loggedinaccount = sprintf("SELECT account_id,fk_account_rank_id,(UNIX_TIMESTAMP(CURRENT_TIMESTAMP) - UNIX_TIMESTAMP(last_active)) FROM Accounts WHERE sessionid='%s'",
			mysql_real_escape_string($this->sessionid));

		$result_set = $this->exec_query($select_loggedinaccount);
		if (mysql_num_rows($result_set) == 0)
		{
			$this->error("Not authenticated. No account with the sessionid='$this->sessionid' was found.");
		}

		$row = mysql_fetch_assoc($result_set);
		$id = $row["account_id"];
		$seconds = $row["(UNIX_TIMESTAMP(CURRENT_TIMESTAMP) - UNIX_TIMESTAMP(last_active))"];

		// Check if the sessionid has expired, if not renew it
		if($seconds > $this->max_idle_time)
		{
			$this->error("Session, '$this->sessionid', timed out.");
		}
		else
		{
			$update_session = sprintf("UPDATE Accounts SET last_active=CURRENT_TIMESTAMP WHERE account_id=%d",
				mysql_real_escape_string($id));
			$this->exec_query($update_session);
		}

		mysql_free_result($result_set);

		return $row;
	}

	/**
	 * Webservice call: Set the user's profile name in the MySql database.
	 * @param $name The new username.
	 * @throws ErrorException
	 */ 
	public function setName($name)
	{
		$update_account = sprintf("UPDATE Accounts SET name='%s' WHERE account_id=%d",
			mysql_real_escape_string($name),
			mysql_real_escape_string($this->account_id));

		$result = $this->exec_query($update_account);

		if(mysql_affected_rows() == 0)
		{
			$this->error("Couldn't set name='$name' with id='$this->account_id'");
		}

		error_log("PW_LOG: set name='$name' with id='$this->account_id'\n", 3, $this->error_log_file);

		$output = array('PW_SUCCESS' => array('msg' => 'Set name to $name.'));
		return json_encode($output);
	}

	/**
	 * Webservice call: Get the current user's name.
	 * @throws ErrorException
	 */ 
	public function getName()
	{
		$select_name = sprintf("SELECT name FROM Accounts WHERE account_id=%d",
		mysql_real_escape_string($this->account_id));

		$result = $this->exec_query($select_name);
		$entry = mysql_fetch_assoc($result);
		$data['name'] = $entry["name"];

		$output = array('PW_SUCCESS' => $data);
		return json_encode($output);
	}

	/**
	 * Webservice call: Get's the current user's id.n
	 */ 
	public function getId()
	{
		$data['id'] = $this->account_id;

		$output = array('PW_SUCCESS' => $data);
		return json_encode($output);
	}

	/**
	 * Webservice call: Create a new Deathmatch using the match info provided.
	 * @param $match_info The initial values for the new match.
	 * @throws ErrorException
	 */ 
	public function createMatchDM($match_info)
	{
		// Get the MatcheTypes id for Deathmatch
		$select_type = "SELECT match_type_id FROM MatchTypes WHERE name='Deathmatch'";

		$result = $this->exec_query($select_type);
		if (mysql_num_rows($result) == 0)
		{
			$this->error("Match type was not found.");
		}
		$entry = mysql_fetch_assoc($result);
		$match_type = $entry["match_type_id"];

		// Insert a new match into Matches
		$insert_match = sprintf("INSERT INTO Matches (fk_account_id,fk_match_type_id,name,max_players,num_players, gun_radius,gun_range) VALUES (%d,%d,'%s',%d,%d, %d,%d)",
			mysql_real_escape_string($this->account_id),
			mysql_real_escape_string($match_type),
			mysql_real_escape_string($match_info['match_name']),
			mysql_real_escape_string($match_info['max_players']),
			0,
			mysql_real_escape_string($match_info['gun_radius']),
			mysql_real_escape_string($match_info['gun_range']));

		$this->exec_query($insert_match);

		// Insert a new Deathmatch into Deathmatches
		$insert_dm = sprintf("INSERT INTO Deathmatches (fk_match_id,max_points) VALUES (%d,%d)",
			mysql_real_escape_string(mysql_insert_id()),
			mysql_real_escape_string($match_info['dm_max_points']));

		$this->exec_query($insert_dm);

		$output = array('PW_SUCCESS' => array('msg' => 'Created new Deathmatch.'));
		return json_encode($output);
	}

	/**
	 * Webservice call: Get a list of all the matches in the matches table.
	 * @throws ErrorException
	 */ 
	public function getMatches()
	{
		$select_matches = "SELECT m.match_id, a.name, m.name, CONCAT(num_players,'/',max_players), mt.name FROM Accounts a, Matches m, MatchTypes mt WHERE a.account_id=m.fk_account_id AND mt.match_type_id=m.fk_match_type_id";
		$result_set = $this->exec_query($select_matches);

		while($entry = mysql_fetch_row($result_set))
		{
			//var_dump($entry);
			$match['id'] = $entry[0];
			$match['host'] = $entry[1];
			$match['name'] = $entry[2];
			$match['count'] = $entry[3];
			$match['type'] = $entry[4];
			$jarray[] = $match;
		}
		$output = array('PW_SUCCESS' => $jarray);
		return json_encode($output);
	}

	/**
	 * Webservice call: Obtain match info based on match_id provided.
	 * @param $match_id The id of the match you wish to obtain information about.
	 * @throws ErrorException
	 */ 
	public function getMatch($match_id)
	{
		$select_match = sprintf("SELECT m.match_id, a.name, m.name, CONCAT(num_players,'/',max_players), mt.name, m.gun_radius, m.gun_range, m.fk_account_id FROM Accounts a, Matches m, MatchTypes mt WHERE m.match_id=%d AND a.account_id=m.fk_account_id AND mt.match_type_id=m.fk_match_type_id",
			mysql_real_escape_string($match_id));
		$result_set = $this->exec_query($select_match);

		if($entry = mysql_fetch_row($result_set))
		{
			$match['id'] = $entry[0];
			$match['host'] = $entry[1];
			$match['name'] = $entry[2];
			$match['count'] = $entry[3];
			$match['type'] = $entry[4];
			$match['gun_radius'] = $entry[5];
			$match['gun_range'] = $entry[6];
			$match['rank'] = $this->account_rank;
			if((int)$this->account_id == (int)$entry[7])
			{
				$match['owner'] = true;
			}
			else
			{
				$match['owner'] = false;
			}

			$output = array('PW_SUCCESS' => $match);
			return json_encode($output);
		}

		$this->error("Could not get match with id=$match_id");
	}

	/**
	 * Webservice call: Obtain default values for all match types.
	 * @throws ErrorException
	 */ 
	public function getMatchTypeDefaults()
	{
		$select_match_types = "SELECT match_type_id, name, default_max_players, default_gun_radius, default_gun_range FROM MatchTypes";

		$result_set = $this->exec_query($select_match_types);

		while($entry = mysql_fetch_row($result_set))
		{
			$match_type['match_type_id'] = $entry[0];
			$match_type['name'] = $entry[1];
			$match_type['default_max_players'] = $entry[2];
			$match_type['default_gun_radius'] = $entry[3];
			$match_type['default_gun_range'] = $entry[4];
			$jarray[] = $match_type;
		}
		$output = array('PW_SUCCESS' => $jarray);
		return json_encode($output);
	}

	/**
	 * Webservice call: Add user to match with the provided match_id.
	 * @param $match_id The id of the match the user wishes to join.
	 * @throws ErrorException
	 */ 
	public function joinMatch($match_id)
	{
		$select_match = sprintf("SELECT max_players,num_players FROM Matches WHERE match_id=%d",
			mysql_real_escape_string($match_id));
		$result = $this->exec_query($select_match);

		if (mysql_num_rows($result) == 0)
		{
			$this->error("No match with match_id=$match_id was found.");
		}

		$entry = mysql_fetch_assoc($result);
		$max_players = (int)$entry["max_players"];
		$num_players = (int)$entry["num_players"];

		if($num_players >= $max_players)
		{
			$this->error("Match is full.");
		}

		$update_player = sprintf("UPDATE Accounts SET fk_match_id=%d, match_num_kills=0, match_num_deaths=0, match_gun_hits=0, match_gun_misses=0, match_mine_hits=0, match_mine_misses=0 WHERE account_id=%d",
			mysql_real_escape_string($match_id),
			mysql_real_escape_string($this->account_id));
		$result = $this->exec_query($update_player);

		$update_num_players = sprintf("UPDATE Matches SET num_players=num_players+1 WHERE match_id=%d",
			mysql_real_escape_string($match_id));
		$this->exec_query($update_num_players);

		// Notify everyone that you joined
		$this->joinMatch_Notify($match_id);

		// Send back all the current match statistics and player list
		$select_stats = sprintf("SELECT account_id,name,match_num_kills,match_num_deaths FROM Accounts WHERE fk_match_id=%d",
			mysql_real_escape_string($match_id));
		$result_set = $this->exec_query($select_stats);

		while($entry = mysql_fetch_assoc($result_set))
		{
			$pstats['id'] = $entry['account_id'];
			$pstats['name'] = $entry['name'];
			$pstats['kills'] = $entry['match_num_kills'];
			$pstats['deaths'] = $entry['match_num_deaths'];
			$jarray[] = $pstats;
		}
		$output = array('PW_SUCCESS' => $jarray);
		return json_encode($output);

		//$output = array('PW_SUCCESS' => array('msg' => "Joined match with match_id=$match_id."));
		//return json_encode($output);
	}

	/**
	 * Notify all user's of a UPDATE_PLAYER_JOINED event.
	 * @param $match_id The id of the match the user joined.
	 * @throws ErrorException
	 */ 
	public function joinMatch_Notify($match_id)
	{
		// Select all players
		$select_players = sprintf("SELECT account_id FROM Accounts WHERE fk_match_id=%d AND account_id!=%d",
			mysql_real_escape_string($match_id),
			mysql_real_escape_string($this->account_id));

		$players_result_set = $this->exec_query($select_players);

		// Notify everyone in the match that someone has joined the match
		while($p = mysql_fetch_assoc($players_result_set))
		{
			// Insert into the update table that someone was killed by a mine
			$insert_update = sprintf("INSERT INTO Updates (fk_account_id,utype,custom1_int) VALUES (%d,%d,%d)",
				mysql_real_escape_string($p['account_id']),
				mysql_real_escape_string(UPDATE_PLAYER_JOINED),
				mysql_real_escape_string($this->account_id));

			$result = $this->exec_query($insert_update);
		}
	}

	/**
	 * Get the player's match updates.
	 * @throws ErrorException
	 */ 
	private function selectPlayerUpdates()
	{
		$select_updates = sprintf("SELECT utype,custom1_int,custom2_int,custom1_double,custom2_double FROM Updates WHERE fk_account_id=%d",
			mysql_real_escape_string($this->account_id));

		$result_set = $this->exec_query($select_updates);

		// Check if it found any updates for this player
		if (mysql_num_rows($result_set) == 0)
		{
			return array();
		}

		while($entry = mysql_fetch_assoc($result_set))
		{
			$message = "Update Type: ".$entry['utype'];
			error_log("PW_ERROR: ".$message."\n", 3, $this->error_log_file);

			switch((int)$entry['utype'])
			{
				case UPDATE_MINE_LAY:
					$jarray[] = $this->formatUpdatePosition($entry);
					break;
				case UPDATE_KILLED_BY_MINE:
					$jarray[] = $this->formatUpdateKilledByMine($entry);
					break;
				case UPDATE_PLAYER_JOINED:
					$jarray[] = $this->formatUpdatePlayerJoined($entry);
					break;
				case UPDATE_KILLED_BY_GUN:
					$jarray[] = $this->formatUpdateKilledByGun($entry);
					break;
				case UPDATE_LEAVE_MATCH:
					$jarray[] = $this->formatUpdateLeaveMatch($entry);
					break;
				case UPDATE_GAME_OVER:
					$jarray[] = $this->formatUpdateGameOver($entry);
					break;
			}
		}

		$this->deletePlayerUpdates();

		return $jarray;
	}

	/**
	 * Format the UPDATE_MINE_LAY event to send to the client.
	 * @param $entry The UPDATE_MINE_LAY information to format.
	 */ 
	private function formatUpdatePosition($entry)
	{
		$info['type'] = UPDATE_MINE_LAY;
		$info['latitude'] = $entry['custom1_double'];
		$info['longitude'] = $entry['custom2_double'];

		return $info;
	}

	/**
	 * Format the UPDATE_KILLED_BY_MINE event to send to the client.
	 * @param $entry The UPDATE_KILLED_BY_MINE information to format.
	 */ 
	private function formatUpdateKilledByMine($entry)
	{
		$info['type'] = UPDATE_KILLED_BY_MINE;
		$info['player_id'] = $entry['custom1_int'];
		$info['enemy_id'] = $entry['custom2_int'];

		return $info;
	}

	/**
	 * Format the UPDATE_PLAYER_JOINED event to send to the client.
	 * @param $entry The UPDATE_PLAYER_JOINED information to format.
	 */ 
	private function formatUpdatePlayerJoined($entry)
	{
		$info['type'] = UPDATE_PLAYER_JOINED;
		$info['player_id'] = $entry['custom1_int'];

		// Get name for the current player
		$select_name = sprintf("SELECT name FROM Accounts WHERE account_id=%d",
			mysql_real_escape_string($info['player_id']));
		$result = $this->exec_query($select_name);
		$player_info = mysql_fetch_assoc($result);

		$info['name'] = $player_info['name'];

		return $info;
	}

	/**
	 * Format the UPDATE_KILLED_BY_GUN event to send to the client.
	 * @param $entry The UPDATE_KILLED_BY_GUN information to format.
	 */ 
	private function formatUpdateKilledByGun($entry)
	{
		$info['type'] = UPDATE_KILLED_BY_GUN;
		$info['player_id'] = $entry['custom1_int'];
		$info['enemy_id'] = $entry['custom2_int'];

		return $info;
	}

	/**
	 * Format the UPDATE_LEAVE_MATCH event to send to the client.
	 * @param $entry The UPDATE_LEAVE_MATCH information to format.
	 */ 
	private function formatUpdateLeaveMatch($entry)
	{
		$info['type'] = UPDATE_LEAVE_MATCH;
		$info['player_id'] = $entry['custom1_int'];

		return $info;
	}

	/**
	 * Format the UPDATE_GAME_OVER event to send to the client.
	 * @param $entry The UPDATE_GAME_OVER information to format.
	 */ 
	private function formatUpdateGameOver($entry)
	{
		$info['type'] = UPDATE_GAME_OVER;
		$info['player_id'] = $entry['custom1_int'];

		return $info;
	}
	
	/**
	 * Delete all player update entries in the Updates table.
	 * @throws ErrorException
	 */ 
	private function deletePlayerUpdates()
	{
		$delete_updates = sprintf("DELETE FROM Updates WHERE fk_account_id=%d",
			mysql_real_escape_string($this->account_id));

		$result = $this->exec_query($delete_updates);
	}

	/**
	 * Webservice call: Update the current player's position.
	 * @param $latitude The player's latitude
	 * @param $longitude The player's longitude
	 * @throws ErrorException
	 */ 
	public function updatePosition($latitude, $longitude)
	{
		$msg = "Update Position: (".(double)$latitude.",".(double)$longitude.")";
		//error_log("PW_POSITION: ".$msg."\n", 3, "./pw_error.log");

		$select_match = sprintf("SELECT fk_match_id FROM Accounts WHERE account_id=%d",
			mysql_real_escape_string($this->account_id));
		$result = $this->exec_query($select_match);
		$match = mysql_fetch_assoc($result);

		$msg = "match_id: ".$match['fk_match_id'];
		error_log("PW_POSITION: ".$msg."\n", 3, "./pw_error.log");

		if($match['fk_match_id'] == NULL) {
			$this->error('player is not in a match');
		}

		$update_player = sprintf("UPDATE Accounts SET match_latitude=%.8lf, match_longitude=%.8lf WHERE account_id=%d",
			mysql_real_escape_string($latitude),
			mysql_real_escape_string($longitude),
			mysql_real_escape_string($this->account_id));

		//error_log("PW_POSITION: ".$update_player."\n", 3, "./pw_error.log");

		$result_set = $this->exec_query($update_player);

		$updates = $this->selectPlayerUpdates();

		$output = array('PW_SUCCESS' => $updates);
		return json_encode($output);
	}

	/**
	 * Webservice call: Check to see if player's gun shot hit another player.
	 * @param $x The shooter's latitude
	 * @param $y The shooter's longitude
	 * @throws ErrorException
	 */ 
	public function updateGunShot($x, $y)
	{
		// Get match id for the current player
		$select_player_info = sprintf("SELECT fk_match_id, match_latitude, match_longitude FROM Accounts WHERE account_id=%d",
			mysql_real_escape_string($this->account_id));

		$result = $this->exec_query($select_player_info);

		$player = mysql_fetch_assoc($result);

		// Select all players and their locations
		$select_enemies = sprintf("SELECT account_id, match_latitude, match_longitude FROM Accounts WHERE fk_match_id=%d AND account_id!=%d",
			mysql_real_escape_string($player['fk_match_id']),
			mysql_real_escape_string($this->account_id));

		$result_set = $this->exec_query($select_enemies);

		while($enemy = mysql_fetch_assoc($result_set))
		{
			if($this->updateGunShot_IsShot($player, $enemy, (double)$x, (double)$y))
			{
				$this->updateGunShot_UpdateScores($player, $enemy);
				$this->updateGunShot_UpdateHits();
				$this->updateGunShot_Notify($player, $enemy);
				//$output = array('PW_SUCCESS' => array("holy moly you must be the good guy"));
				//return json_encode($output);
			}
			else
			{
				$this->updateGunShot_UpdateMisses();
			}
		}
		//$this->error("you have bad guy aim");
		
		// Get all the current match updates for the player
		$updates = $this->selectPlayerUpdates();
		$output = array('PW_SUCCESS' => $updates);
		return json_encode($output);
	}

	/**
	 * Update the player's and enemy's scores. This is called when there is a hit.
	 * @param $player Information about the player
	 * @param $enemy Information about the enemy
	 * @throws ErrorException
	 */ 
	public function updateGunShot_UpdateScores($player, $enemy)
	{
		// Increase player's score by 1
		$update_player = sprintf("UPDATE Accounts SET match_num_kills=match_num_kills+1, match_gun_hits=match_gun_hits WHERE account_id=%d",
			mysql_real_escape_string($this->account_id));
		$result_set = $this->exec_query($update_player);

		// Decrease enemy's score by 1
		$update_enemy = sprintf("UPDATE Accounts SET match_num_deaths=match_num_deaths+1 WHERE account_id=%d",
			mysql_real_escape_string($enemy['account_id']));
		$result_set = $this->exec_query($update_enemy);

		$get_max_points = sprintf("SELECT max_points FROM Deathmatches WHERE fk_match_id=%d",
			mysql_real_escape_string($player['fk_match_id']));
		$result_set = $this->exec_query($get_max_points);

		$entry = mysql_fetch_assoc($result_set);
		$max_points = (int)$entry["max_points"];

		$check_win = sprintf("SELECT account_id FROM Accounts WHERE match_num_kills-match_num_deaths >= %d",
			mysql_real_escape_string($max_points));
		$result_set = $this->exec_query($check_win);

		if (mysql_num_rows($result_set) > 0)
		{
			$this->gameOver_Notify($player['fk_match_id']);
		}
	}

	/**
	 * Notify all players that the game is over.
	 * @param $match_id The id of the match.
	 * @throws ErrorException
	 */ 
	public function gameOver_Notify($match_id)
	{
		// Select all players
		$select_players = sprintf("SELECT account_id FROM Accounts WHERE fk_match_id=%d",
			mysql_real_escape_string($match_id));

		$players_result_set = $this->exec_query($select_players);

		// Notify everyone in the match that a kill by mine occured
		while($p = mysql_fetch_assoc($players_result_set))
		{
			// Insert into the update table that someone was killed by a mine
			$insert_update = sprintf("INSERT INTO Updates (fk_account_id,utype,custom1_int) VALUES (%d,%d,%d)",
				mysql_real_escape_string($p['account_id']),
				mysql_real_escape_string(UPDATE_GAME_OVER),
				mysql_real_escape_string($this->account_id));

			$result = $this->exec_query($insert_update);
		}
	}

	/**
	 * Update gun hits statistic of current player.
	 * @throws ErrorException
	 */ 
	public function updateGunShot_UpdateHits()
	{
		// Increase player's hits by 1
		$update_hits = sprintf("UPDATE Accounts SET match_gun_hits=match_gun_hits+1 WHERE account_id=%d",
			mysql_real_escape_string($this->account_id));
		$result_set = $this->exec_query($update_hits);
	}

	/**
	 * Update gun misses statistic of current player.
	 * @throws ErrorException
	 */ 
	public function updateGunShot_UpdateMisses()
	{
		// Increase player's misses by 1
		$update_misses = sprintf("UPDATE Accounts SET match_gun_misses=match_gun_misses+1 WHERE account_id=%d",
			mysql_real_escape_string($this->account_id));
		$result_set = $this->exec_query($update_misses);
	}

	/**
	 * Check to see whether or not the player shot an enemy.
	 * @param $player The player information
	 * @param $enemy The enemy information
	 * @param $x The direction of the gun shot x-component
	 * @param $y The direction of the gun shot y-component
	 * @throws ErrorException
	 */ 
	public function updateGunShot_IsShot($player, $enemy, $x, $y)
	{
		$cx = (double)$player['match_latitude'];
		$cy = (double)$player['match_longitude'];

		// Get match id for the current player
		$select_gun_info = sprintf("SELECT gun_range, gun_radius FROM Matches WHERE match_id=%d",
			mysql_real_escape_string($player['fk_match_id']));

		$result = $this->exec_query($select_gun_info);
		$gun_info = mysql_fetch_assoc($result);

		$oldx = (double)$enemy['match_latitude'];
		$oldy = (double)$enemy['match_longitude'];

		// Get width => match_gun_radius, height => match_gun_range
		//$width = 40.50001500;
		//$height = 40.00300000;
		$width = (double)$gun_info['gun_radius'];
		$height = (double)$gun_info['gun_range'];

		$centerx = $cx + ($x * ($height/2.0));
		$centery = $cy + ($y * ($height/2.0));

		$angle = (double)acos($x*1.0 + $y*0.0);

		$message = "--------------player--------------\n".
		"latitude: ".$player['match_latitude']."\n".
		"longitude: ".$player['match_longitude']."\n".
		"fk_match_id: ".$player['fk_match_id']."\n".
		"--------------enemy--------------\n".
		"latitude: ".$enemy['match_latitude']."\n".
		"longitude: ".$enemy['match_longitude']."\n".
		"fk_match_id: ".$enemy['account_id']."\n".
		"--------------box--------------\n".
		"oldx: ".$oldx."\n".
		"oldy: ".$oldy."\n".
		"centerx: ".$centerx."\n".
		"centery: ".$centery."\n".
		"angle: ".$angle."\n".
		"width: ".$width."\n".
		"height: ".$height."\n";

		//error_log("PW_GUN: ".$message."\n", 3, $this->error_log_file);

		return $this->pointInBox($oldx, $oldy, $centerx, $centery, $angle, $width, $height);
	}

	/**
	 * Notify all player's of gun shot hit.
	 * @param $player The player information
	 * @param $enemy The enemy information
	 * @throws ErrorException
	 */ 
	public function updateGunShot_Notify($player, $enemy)
	{
		// Select all players
		$select_players = sprintf("SELECT account_id FROM Accounts WHERE fk_match_id=%d",
			mysql_real_escape_string($player['fk_match_id']));

		$players_result_set = $this->exec_query($select_players);

		// Notify everyone in the match that a kill by mine occured
		while($p = mysql_fetch_assoc($players_result_set))
		{
			// Insert into the update table that someone was killed by a mine
			$insert_update = sprintf("INSERT INTO Updates (fk_account_id,utype,custom1_int,custom2_int) VALUES (%d,%d,%d,%d)",
				mysql_real_escape_string($p['account_id']),
				mysql_real_escape_string(UPDATE_KILLED_BY_GUN),
				mysql_real_escape_string($this->account_id),
				mysql_real_escape_string($enemy['account_id']));

			$result = $this->exec_query($insert_update);
		}
	}

	/**
	 * Convert point to box coordinate system and do AABB test
	 * @param $oldx The point's x-component
	 * @param $oldy The point's y-component
	 * @param $centerx The box's x-component
	 * @param $centery The box's y-component
	 * @param $angle The box's rotation angle
	 * @param $width The box's width
	 * @param $height The box's height
	 * @throws ErrorException
	 */ 
	public function pointInBox($oldx, $oldy, $centerx, $centery, $angle, $width, $height)
	{
		$newy = sin($angle) * ($oldy - $centery) + cos($angle) * ($oldx - $centerx);
		$newx = cos($angle) * ($oldx - $centerx) - sin($angle) * ($oldy - $centery);

		$message = "--------------pointInBox--------------\n".
		"newx: ".$newx."\n".
		"newy: ".$newy."\n";

		//error_log("PW_GUN: ".$message."\n", 3, $this->error_log_file);

		return ($newy > -$height / 2.0) && ($newy < $height / 2.0) && ($newx > -$width / 2.0) && ($newx < $width / 2.0);
	}

	/**
	 * Webservice call: Add a mine to the mine table for the current player.
	 * @throws ErrorException
	 */ 
	public function updateMineLay()
	{
		// Get the location of the player
		$select_player_info = sprintf("SELECT match_latitude, match_longitude FROM Accounts WHERE account_id=%d",
			mysql_real_escape_string($this->account_id));

		$result = $this->exec_query($select_player_info);
		$player = mysql_fetch_assoc($result);

		$msg = "Laying Mine: (".$player['match_latitude'].",".$player['match_longitude'].")";
		//error_log("PW_MINE: ".$msg."\n", 3, "./pw_error.log");

		// Lay the mine, insert it into the Mines table
		$insert_mine = sprintf("INSERT INTO Mines (fk_account_id,latitude,longitude) VALUES (%d,%.8lf,%.8lf)",
			mysql_real_escape_string($this->account_id),
			mysql_real_escape_string($player['match_latitude']),
			mysql_real_escape_string($player['match_longitude']));

		$result = $this->exec_query($insert_mine);

		// Insert into the update table that the mine was laid
		$insert_update = sprintf("INSERT INTO Updates (fk_account_id,utype,custom1_double,custom2_double) VALUES (%d,%d,%.8lf,%.8lf)",
			mysql_real_escape_string($this->account_id),
			mysql_real_escape_string(UPDATE_MINE_LAY),
			mysql_real_escape_string($player['match_latitude']),
			mysql_real_escape_string($player['match_longitude']));

		$result = $this->exec_query($insert_update);

		// Get all the current match updates for the player
		$updates = $this->selectPlayerUpdates();
		$output = array('PW_SUCCESS' => $updates);
		return json_encode($output);
	}

	/**
	 * Webservice call: Detonate all of the current user's placed mines.
	 * @throws ErrorException
	 */ 
	public function updateMineDetonate()
	{
		// Get match id for the current player
		$select_player_info = sprintf("SELECT fk_match_id FROM Accounts WHERE account_id=%d",
			mysql_real_escape_string($this->account_id));

		$result = $this->exec_query($select_player_info);
		$player = mysql_fetch_assoc($result);

		// Get all the player's mines
		$select_mines = sprintf("SELECT latitude, longitude FROM Mines WHERE fk_account_id=%d",
			mysql_real_escape_string($this->account_id));

		$mine_result_set = $this->exec_query($select_mines);

		// Select all players and their locations
		$select_enemies = sprintf("SELECT account_id, match_latitude, match_longitude FROM Accounts WHERE fk_match_id=%d AND account_id!=%d",
			mysql_real_escape_string($player['fk_match_id']),
			mysql_real_escape_string($this->account_id));

		$enemy_result_set = $this->exec_query($select_enemies);

		while($mine = mysql_fetch_assoc($mine_result_set))
		{
			while($enemy = mysql_fetch_assoc($enemy_result_set))
			{
				if($this->updateMineDetonate_IsBlownUp($mine, $enemy))
				{
					$this->updateMineDetonate_UpdateScores($player, $enemy);
					$this->updateMineDetonate_UpdateHits();
					$this->updateMineDetonate_Notify($player, $enemy);
				}
				else
				{
					$this->updateMineDetonate_UpdateMisses();
				}
			}
		}

		$this->updateMineDetonate_DeleteMines();

		// Get all the current match updates for the player
		$updates = $this->selectPlayerUpdates();
		$output = array('PW_SUCCESS' => $updates);
		return json_encode($output);
	}

	/**
	 * Update the player and enemy score. This is called when there is a mine hit.
	 * @param $player The player information
	 * @param $enemy The enemy information
	 * @throws ErrorException
	 */ 
	public function updateMineDetonate_UpdateScores($player, $enemy)
	{
		// Increase player's score by 1
		$update_player = sprintf("UPDATE Accounts SET match_num_kills=match_num_kills+1, match_gun_hits=match_gun_hits WHERE account_id=%d",
			mysql_real_escape_string($this->account_id));
		$result_set = $this->exec_query($update_player);

		// Decrease enemy's score by 1
		$update_enemy = sprintf("UPDATE Accounts SET match_num_deaths=match_num_deaths+1 WHERE account_id=%d",
			mysql_real_escape_string($enemy['account_id']));
		$result_set = $this->exec_query($update_enemy);

		$get_max_points = sprintf("SELECT max_points FROM Deathmatches WHERE fk_match_id=%d",
			mysql_real_escape_string($player['fk_match_id']));
		$result_set = $this->exec_query($get_max_points);

		$entry = mysql_fetch_assoc($result_set);
		$max_points = (int)$entry["max_points"];

		$check_win = sprintf("SELECT account_id FROM Accounts WHERE match_num_kills-match_num_deaths >= %d",
			mysql_real_escape_string($max_points));
		$result_set = $this->exec_query($check_win);

		if (mysql_num_rows($result_set) > 0)
		{
			$this->gameOver_Notify($player['fk_match_id']);
		}
	}

	/**
	 * Update the current user's mine hits statistic.
	 * @throws ErrorException
	 */ 
	public function updateMineDetonate_UpdateHits()
	{
		// Increase player's hits by 1
		$update_hits = sprintf("UPDATE Accounts SET match_mine_hits=match_mine_hits+1 WHERE account_id=%d",
			mysql_real_escape_string($this->account_id));
		$result_set = $this->exec_query($update_hits);
	}

	/**
	 * Update the current user's mine misses statistic.
	 * @throws ErrorException
	 */ 
	public function updateMineDetonate_UpdateMisses()
	{
		// Increase player's misses by 1
		$update_misses = sprintf("UPDATE Accounts SET match_mine_misses=match_mine_misses+1 WHERE account_id=%d",
			mysql_real_escape_string($this->account_id));
		$result_set = $this->exec_query($update_misses);
	}

	/**
	 * Checks to see if mine blew up an enemy.
	 * @param $mine Mine information
	 * @param $enemy Enemy information
	 * @throws ErrorException
	 */ 
	public function updateMineDetonate_IsBlownUp($mine, $enemy)
	{
		//TODO: keep this in the database?
		// Get the mine radius
		$radius = (double)40.0; // feet

		// Check if enemy is within range of the mine
		$distance = $this->haversine_feet((double)$mine['latitude'], (double)$mine['longitude'], (double)$enemy['match_latitude'], (double)$enemy['match_longitude']);
		if($distance <= $radius)
		{
			return TRUE;
		}
		return FALSE;
	}

	/**
	 * Notify all players of mine hit.
	 * @param $player Player information
	 * @param $enemy Enemy information
	 * @throws ErrorException
	 */ 
	public function updateMineDetonate_Notify($player, $enemy)
	{
		// Select all players and their locations
		$select_players = sprintf("SELECT account_id FROM Accounts WHERE fk_match_id=%d",
			mysql_real_escape_string($player['fk_match_id']));

		$players_result_set = $this->exec_query($select_players);

		// Notify everyone in the match that a kill by mine occured
		while($p = mysql_fetch_assoc($players_result_set))
		{
			// Insert into the update table that someone was killed by a mine
			$insert_update = sprintf("INSERT INTO Updates (fk_account_id,utype,custom1_int,custom2_int) VALUES (%d,%d,%d,%d)",
				mysql_real_escape_string($p['account_id']),
				mysql_real_escape_string(UPDATE_KILLED_BY_MINE),
				mysql_real_escape_string($this->account_id),
				mysql_real_escape_string($enemy['account_id']));

			$result = $this->exec_query($insert_update);
		}
	}

	/**
	 * Delete all current user's mines from the Mines table.
	 * @throws ErrorException
	 */ 
	public function updateMineDetonate_DeleteMines()
	{
		$delete_mines = sprintf("DELETE FROM Mines WHERE fk_account_id=%d",
			mysql_real_escape_string($this->account_id));

		$result = $this->exec_query($delete_mines);
	}

	/**
	 * Get the distance between two gps fixes in feet.
	 * @param $lat1 The first gps fix's latitude
	 * @param $long1 The first gps fix's longitude
	 * @param $lat2 The first gps fix's latitude
	 * @param $long2 The first gps fix's longitude
	 * @throws ErrorException
	 */ 
	public function haversine_feet($lat1, $long1, $lat2, $long2)
	{
		$d2r = (double)M_PI / 180.0;
		$dlong = (double)($long2 - $long1) * $d2r;
		$dlat = (double)($lat2 - $lat1) * $d2r;
		$a = (double)pow(sin($dlat/2.0), 2) + cos($lat1*$d2r) * cos($lat2*$d2r) * pow(sin($dlong/2.0), 2);
		$c = (double)2 * atan2(sqrt($a), sqrt(1-$a));
		$d = (double)6367 * $c;

		// convert km to ft
		return $d*3280.8399;
	}

	/**
	 * Webservice call: Remove player from match.
	 * @throws ErrorException
	 */ 
	public function updateLeaveMatch()
	{
		// Get match id for the current player
		$select_player_info = sprintf("SELECT fk_match_id FROM Accounts WHERE account_id=%d",
			mysql_real_escape_string($this->account_id));
		$result = $this->exec_query($select_player_info);
		$player = mysql_fetch_assoc($result);

		// Set player's fk_match_id to NULL
		$update_player = sprintf("UPDATE Accounts SET fk_match_id=NULL WHERE account_id=%d",
			mysql_real_escape_string($this->account_id));
		$result_set = $this->exec_query($update_player);

		// Decrement the number of players in the match
		$update_match = sprintf("UPDATE Matches SET num_players=num_players-1 WHERE match_id=%d",
			mysql_real_escape_string($player['fk_match_id']));
		$result_set = $this->exec_query($update_match);

		// Remove all the players mines
		$this->updateMineDetonate_DeleteMines();

		$this->updateLeaveMatch_Notify($player);

		// Remove all the remaining player updates
		$this->deletePlayerUpdates();

		// Get all the current match updates for the player
		$updates = $this->selectPlayerUpdates();
		$output = array('PW_SUCCESS' => $updates);
		return json_encode($output);
	}

	/**
	 * Notify all player's of the player leaving the match.
	 * @param $player Player info
	 * @throws ErrorException
	 */ 
	public function updateLeaveMatch_Notify($player)
	{
		// Select all players and their locations
		$select_players = sprintf("SELECT account_id FROM Accounts WHERE fk_match_id=%d",
			mysql_real_escape_string($player['fk_match_id']));

		$players_result_set = $this->exec_query($select_players);

		// Notify everyone in the match that a kill by mine occured
		while($p = mysql_fetch_assoc($players_result_set))
		{
			// Insert into the update table that someone was killed by a mine
			$insert_update = sprintf("INSERT INTO Updates (fk_account_id,utype,custom1_int) VALUES (%d,%d,%d)",
				mysql_real_escape_string($p['account_id']),
				mysql_real_escape_string(UPDATE_LEAVE_MATCH),
				mysql_real_escape_string($this->account_id));

			$result = $this->exec_query($insert_update);
		}
	}

	/**
	 * Delete match from Matches table.
	 * @param $match_id The id of the match to delete
	 * @throws ErrorException
	 */ 
	public function deleteMatch($match_id)
	{
		// Check if the player is the owner of the match
		$select_owner = sprintf("SELECT fk_account_id FROM Matches WHERE match_id=%d",
			mysql_real_escape_string($match_id));
		$result = $this->exec_query($select_owner);
		$data = mysql_fetch_assoc($result);

		// The player does not have permission to delete the account
		if(((int)$data['fk_account_id'] != (int)$this->account_id) && $this->account_rank <= 1)
		{
			$this->error("You do not have permission to delete this match...");
		}

		// Get match id for the current player
		$select_player_info = sprintf("SELECT fk_match_id FROM Accounts WHERE account_id=%d",
			mysql_real_escape_string($this->account_id));
		$result = $this->exec_query($select_player_info);
		$player = mysql_fetch_assoc($result);

		// Select all players
		$select_players = sprintf("SELECT account_id FROM Accounts WHERE fk_match_id=%d",
			mysql_real_escape_string($player['fk_match_id']));
		$players_result_set = $this->exec_query($select_players);

		// Remove all updates and mines for all players
		while($p = mysql_fetch_assoc($players_result_set))
		{
			$delete_updates = sprintf("DELETE FROM Updates WHERE fk_account_id=%d",
				mysql_real_escape_string($p['account_id']));
			$result = $this->exec_query($delete_updates);

			$delete_mines = sprintf("DELETE FROM Mines WHERE fk_account_id=%d",
				mysql_real_escape_string($p['account_id']));
			$result = $this->exec_query($delete_mines);
		}

		// Set player's fk_match_id to NULL
		$update_player = sprintf("UPDATE Accounts SET fk_match_id=NULL WHERE fk_match_id=%d",
			mysql_real_escape_string($match_id));
		$result_set = $this->exec_query($update_player);

		// Delete the match
		$delete_match = sprintf("DELETE FROM Matches WHERE match_id=%d",
			mysql_real_escape_string($match_id));
		$result = $this->exec_query($delete_match);

		$output = array('PW_SUCCESS' => array('msg' => 'Removed match.'));
		return json_encode($output);
	}
}
?>
